using System;
using System.Linq;
using Magiq.Tests.Sql.TestModel;
using Magiq.Tests.Support;
using Magiq.Tests.TestModel;
using NUnit.Framework;

namespace Magiq.Tests
{
    [TestFixture]
    public abstract class BaseMagiqProviderCollectionTest<TParent,TChildItem> where TParent : class, IParent<TChildItem>, new() where TChildItem : class, IChildItem, new()
    {
        protected IMagiqTestConfigurator Configurator { get; private set; }
        protected abstract IMagiqTestConfigurator GetConfigurator();

        protected BaseMagiqProviderCollectionTest()
        {
            Configurator = GetConfigurator();
        }
        #region Setup/Teardown

        [SetUp]
        public void SetUp()
        {
            Configurator.SetUp();
            domain = new CollectionTestDomain<TParent,TChildItem>();
            Configurator.Persist(domain.ChildItems);
            Configurator.Persist(domain.Parents);
            domain.AddRelations();
            Configurator.Reload();
            parents = Configurator.GetQueryable<TParent>();
        }

        [TearDown]
        public void TearDown()
        {
            Configurator.TearDown();
        }

        #endregion

        private CollectionTestDomain<TParent, TChildItem> domain;
        private IQueryable<TParent> parents;

        [Test]
        public void ShouldDeleteFromCollection()
        {
            var collection = parents.ToList().First().Query(x=>x.Items);
            var items = collection.Where(x => x.Name == "lala");
            var count = items.Count();
            items.Delete().Should(Be.EqualTo(count));
        }

        [Test]
        public void ShouldDeleteFromCollectionInExpression()
        {
            var item = parents.ToList().First();
            var count = item.Query(x => x.Items).Count(x => x.Name == "lala");

            item.DeleteCollection(x=>x.Items.Where(i => i.Name == "lala")).Should(Be.EqualTo(count));
        }

        [Test]
        public void ShouldUpdateFromCollectionInExpression()
        {
            var item = parents.ToList().First();
            var count = item.Query(x => x.Items).Count(x => x.Name == "lala");

            item.UpdateCollection(x => x.Items.Where(i => i.Name == "lala").Set(s => s.Name, "lele")).Should(Be.EqualTo(count));
        }

        [Test]
        public void ShouldInsertIntoNoQueryableButDeferredEntitySet()
        {
            var item = parents.ToList().First();
            var dest = item.Query(x => x.Items);
            //if I apply count() to items, linq-to-sql loads the entire collection and it became not deferred
            var prevCount = dest.Count();

            var count = parents.Count();
            var insertCount = dest.Insert(parents.Select(x => new TChildItem
                                                                 {
                                                                     Name = x.Name + " child"
                                                                 }));

            insertCount.Should(Be.EqualTo(count));
            dest.Count().Should(Be.EqualTo(prevCount + insertCount));
        }

        [Test]
        public void ShouldInsertIntoCollection()
        {
            var source = parents.ToList().First();
            var dest = source.Query(x => x.Items);
            //if I apply count() to items, linq-to-sql loads the entire collection and it became not deferred
            var prevCount = dest.Count();

            var count = parents.Count();

            var insertCount = source.InsertInto(x => x.Items, parents.Select(x => new TChildItem
                                                                  {
                                                                      Name = x.Name + " child"
                                                                  }));

            insertCount.Should(Be.EqualTo(count));
            dest.Count().Should(Be.EqualTo(prevCount + insertCount));
        }

        [Test]
        public void ShouldInsertFromCollectionInExpression()
        {
            var item = parents.ToList().First();
            var dest = item.Query(x => x.Items);
            var prevCount = dest.Count();
            var count = parents.Count();
            var insertCount = dest.Insert(item.Query(i=>i.Items.Select(x => new TChildItem
                                                                           {
                                                                               Name = x.Name + " child"
                                                                           })));
            insertCount.Should(Be.EqualTo(count));
            dest.Count().Should(Be.EqualTo(prevCount + insertCount));
        }

        [Test]
        public void ShouldInsertFromCollectionInComplexExpression()
        {
            var item = parents.ToList().First();
            var dest = item.Query(x => x.Items);
            var prevCount = dest.Count();
            var count = dest.Count(x => x.Name == "lala");
            var insertCount = dest.Insert(item.Query(i=>i.Items.Where(x => x.Name == "lala").Select(x => new TChildItem
            {
                Name = x.Name + " child"
            })));
            insertCount.Should(Be.EqualTo(count));
            dest.Count().Should(Be.EqualTo(prevCount + insertCount));
        }
     
        [Test]
        [ExpectedException(typeof(InvalidOperationException))]
        public void ShouldNotInsertToCollectionWithWhere()
        {
            var item = parents.ToList().First();
            var dest = item.Query(x => x.Items).Where(x => x.Name == "lala");

            dest.Insert(item.Query(x=>x.Items).Where(x => x.Name == "lala").Select(x => new TChildItem
                                 {
                                     Name = x.Name + " child"
                                 }));
        }

        [Test]
        public void ShouldInsertIntoQueryableCollection()
        {
            var dest = parents.ToList().First().Query(x => x.Items);
            var prevCount = dest.Count();
            var count = parents.Count();
            var insertCount = dest.Insert(parents.Select(x => new TChildItem
                                                                        {
                                                                            Name = x.Name + " child"
                                                                        }));
            insertCount.Should(Be.EqualTo(count));
            dest.Count().Should(Be.EqualTo(prevCount + insertCount));
        }

        [Test]
        public void ShouldNotInsertIntoNotDeferredEntitySet()
        {
            var dest = parents.ToList().First().Items.AsQueryable();
            dest.Count(); //this make Destination not deferred.

            dest.Insert(parents.Select(x => new TChildItem
                                               {
                                                   Name = x.Name + " child"
                                               }));
            MagiqMock.AssertWasUsed();
        }

        [Test]
        public void ShouldMakeEntitySetToQueryable()
        {
            var queryableCollection = parents.ToList().First()
                .Query(x=>x.Items).Where(x => x.Name == "lala").OrderBy(x => x.Id);

            var enumerableCollection = parents.ToList().First()
                .Items.Where(x => x.Name == "lala").OrderBy(x => x.Id);

            Assert.AreNotEqual(queryableCollection.GetType(), enumerableCollection.GetType());

            var list1 = queryableCollection.ToList();
            var lisTestClass2 = enumerableCollection.ToList();

            for (var i = 0; i < list1.Count; i++)
                list1[i].Id.Should(Be.EqualTo(lisTestClass2[i].Id));
        }

        [Test]
        public void ShouldUpdateCollection()
        {
            var collection = parents.ToList().First().Query(x=>x.Items);
            var items = collection.Where(x => x.Name == "lala");
            var allChildItemsWithLala = parents.SelectMany(x => x.Items).Count(x => x.Name == "lala");
            var count = items.Count();
            var itemsUpdated = items.Set(x => x.Name, "lele").Update();
            itemsUpdated.Should(Be.EqualTo(count));
            itemsUpdated.Should(Be.Not.EqualTo(allChildItemsWithLala));
        }
    }
}